﻿using System;
using System.Collections.Generic;
using System.Linq;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.Controversies.BusinessLogic;
using Sustainalytics.Controversies.Service.Exceptions;
using Sustainalytics.Controversies.Service.Models;
using Sustainalytics.Controversies.Service.Util;
using Sustainalytics.ESG.Entities;
using Sustainalytics.Utils;

namespace Sustainalytics.Controversies.Service.BlInvoker
{
    public class BlInvoker:IServiceBlInvoker
    {

        
        private readonly IPlainBl<RPControversyIndicator> _controversyIndicatorBl;
        private readonly IPlainBl<ComparisonGroupControversy> _comparisounGroupBl;
        private readonly IPlainBl<EventIndicator> _eventIndicatorGroupBl;
        private readonly IPlainBl<Incident> _incidentBl;
        private readonly IPlainBl<CorporateData> _corporateDataBl;
        private readonly IPlainBl<IndicatorTemplate> _indicatorTemplateBl;

        public BlInvoker(IPlainBl<RPControversyIndicator> controversyIndicatorBl, 
            IPlainBl<ComparisonGroupControversy> comparisounGroupBl, 
            IPlainBl<EventIndicator> eventIndicatorGroupBl, 
            IPlainBl<Incident> incidentBl, 
            IPlainBl<CorporateData> corporateDataBl,
            IPlainBl<IndicatorTemplate> indicatorTemplateBl)
        {
            _controversyIndicatorBl = controversyIndicatorBl;
            _comparisounGroupBl = comparisounGroupBl;
            _eventIndicatorGroupBl = eventIndicatorGroupBl;
            _incidentBl = incidentBl;
            _corporateDataBl = corporateDataBl;
            _indicatorTemplateBl = indicatorTemplateBl;
        }


        public List<ControversyIndicatorDto> GetCompanyQualitativePerformance(Guid companyId)
        {
            var corpData = _corporateDataBl.ReadWhere(x => x.Id == companyId, null, null, a => a.ControversyResearchState).FirstOrDefault();

            if (corpData == null)
                throw new CorporateDataNotFoundException($"The company with id: {companyId} was not found");

            if (corpData.ControversyResearchState.ResearchState != BusinessResearchProductStates.Entities.ResearchState.ResearchState.ResearchAvailable)
                return new List<ControversyIndicatorDto>();

            var rpControversyIndicators = _controversyIndicatorBl.ReadWhere(a => a.CompanyId == companyId, null, null,
                a => a.EventIndicators, a => a.Template);
            var qualitativePerformance = rpControversyIndicators.SelectMany(a => a.EventIndicators).GroupBy(a => new
            {
                a.AnswerCategory,
                a.ControversyIndicatorId
            }).Select(a => new ControversyIndicatorDto()
            {
                Id = a.Key.ControversyIndicatorId,
                AnswerCategory = a.Key.AnswerCategory,
                EventIndicators = a.Select(ei => new EventIndicatorDto
                {
                    Id = ei.Id,
                    Name = ei.Name,
                    
                    ControversyIndicatorId = ei.ControversyIndicatorId,
                    ControversyIndicatorName = ei.ControversyIndicatorName,
                    ControversyIndicatorNumber = ei.ControversyIndicatorNumber,
                    AnswerCategory = ei.AnswerCategory
                }).ToList()

            }).ToList();
            foreach (var qp in qualitativePerformance)
            {
                var ci = rpControversyIndicators.FirstOrDefault(a => a.Template.Id == qp.Id);
                if(ci==null) continue;
                qp.Name = ci.Template.Name;
                qp.Number = ci.Template.Number;
                qp.ThemeName = ci.Template.ThemeName;
                qp.EventSeverity = ScoreConversion.GetEventSeverity(qp.AnswerCategory);
                qp.OutlookScore = null;
                qp.OutlookAssessment = null;
            }
            return qualitativePerformance;
        }


        public List<ControversyIndicatorDto> GetControversyIndicators(Guid idCompany, string themeName)
        {
            
            List<RPControversyIndicator> cis;

            if (string.IsNullOrEmpty(themeName))
            {
                cis = _controversyIndicatorBl.ReadAllWhere(ci => ci.CompanyId == idCompany).ToList();
            }
            else
            {
                cis=_controversyIndicatorBl.ReadAllWhere(ci => ci.CompanyId == idCompany && ci.Template.ThemeName==themeName).ToList();
                
            }

            
            var eis = _eventIndicatorGroupBl.ReadAllWhere(ei => ei.CompanyId == idCompany)
                    .GroupBy(ei => ei.ControversyIndicatorId)
                    .ToDictionary(g => g.Key, g => g.Select(ei => new EventIndicatorDto
                    {
                        Id = ei.BaseEventIndicatorId,
                        BaseEventIndicatorId = ei.Id,
                        Name = ei.Name,
                        AnswerCategory = ei.AnswerCategory,
                        ControversyIndicatorName = ei.ControversyIndicatorName,
                        OutlookScore = ei.OutlookScore
                    })
                    .ToList());

            var result = new List<ControversyIndicatorDto>();

            foreach (var ci in cis)
            {
                List<EventIndicatorDto> eventIndicators;
                if (!eis.TryGetValue(ci.Template.Id, out eventIndicators))
                    eventIndicators = new List<EventIndicatorDto>(0);

                int? dominantOutlook = GetDominantOutlook(ci.OutlookScore, eventIndicators);

                var ciDto = new ControversyIndicatorDto
                {
                    Id = ci.Template.Id,
                    Name = ci.Template.Name,
                    Number = ci.Template.Number,
                    AnswerCategory = ci.AnswerCategory,
                    EventSeverity = ScoreConversion.GetEventSeverity(ci.AnswerCategory),
                    OutlookScore = dominantOutlook,
                    OutlookAssessment = dominantOutlook.HasValue ? ScoreConversion.GetOutlookAssessment(dominantOutlook.Value) : null,
                    EventIndicators = eventIndicators.OrderByDescending(ei => ei.AnswerCategory).ThenBy(ei => ei.Name).ToList(),
                    ThemeName = ci.Template.ThemeName
                };

                result.Add(ciDto);
            }
            return result;
        }

        private int? GetDominantOutlook(int? outlookScore, List<EventIndicatorDto> eventIndicators)
        {
            List<int> outlookList = new List<int>();

            if(outlookScore.HasValue)
                outlookList.Add(outlookScore.GetValueOrDefault());

            foreach (var ei in eventIndicators)
            {
                if(ei.OutlookScore.HasValue)
                    outlookList.Add(ei.OutlookScore.GetValueOrDefault());
            }

            if (outlookList.Contains(-1))
                return -1;

            if (outlookList.Contains(1))
                return 1;
         
            if (outlookList.Contains(0))
                return 0;
            
            return null;
        }

        public List<ComparisonGroupControversyDto> GetDistributionOfControversyGraphData(Guid idCompany, Guid idControversyIndicator,
            ControversyGroupAssesment assesment)
        {
            var comparison = _comparisounGroupBl.ReadWhere(a => a.CompanyId == idCompany).ToList();//TODO watch for idControversyIndicator
            return comparison.Select(a => new ComparisonGroupControversyDto()
            {
                Category = a.Category,
                CompanyId = a.CompanyId,
                ControversyIndicatorNumber = a.ControversyIndicatorNumber,
                Percent = a.Percent
            }).ToList();
        }


        public EventIndicatorDto GetEventIndicatorDetailData(Guid idCompany, Guid idControversy, Guid idEventIndicator, bool includeAllFields)
        {
            var eventIndicator = _eventIndicatorGroupBl.ReadWhere(
                ei => ei.CompanyId == idCompany && ei.BaseEventIndicatorId == idEventIndicator, null, null, a => a.Id,
                a => a.Name, a => a.ControversyIndicatorId, a => a.ControversyIndicatorName,
                a => a.ControversyIndicatorNumber, a => a.ControversySummary, a => a.AnswerCategory
                ,a=>a.AnswerCategoryJustification,a=>a.ImpactScore,a=>a.ImpactText,a=>a.ImpactComments,
                a=>a.RiskScore,a=>a.RiskText,a=>a.RiskComments,
                a=>a.OutlookScore,a=>a.OutlookText,a=>a.OutlookComments,
                a=>a.PreparednessScore,a=>a.PreparednessText,a=>a.PreparednessComments
                )
                .Select(ei => new EventIndicatorDto()
                {
                    Id = ei.Id,
                    Name = ei.Name,

                    ControversyIndicatorId = ei.ControversyIndicatorId,
                    ControversyIndicatorName = ei.ControversyIndicatorName,
                    ControversyIndicatorNumber = ei.ControversyIndicatorNumber,

                    ControversySummary = ei.ControversySummary,

                    AnswerCategory = ei.AnswerCategory,
                    AnswerCategoryJustification = ei.AnswerCategoryJustification,
                    EventSeverity = ScoreConversion.GetEventSeverity(ei.AnswerCategory),

                    ImpactScore = ei.ImpactScore,
                    ImpactText = ei.ImpactText,
                    ImpactComments = ei.ImpactComments,

                    RiskScore = ei.RiskScore,
                    RiskText = ei.RiskText,
                    RiskComments = ei.RiskComments,

                    OutlookScore = ei.OutlookScore,
                    OutlookAssessment = ei.OutlookScore.HasValue ? ScoreConversion.GetOutlookAssessment(ei.OutlookScore.Value) : null,
                    OutlookText = ei.OutlookText,
                    OutlookComments = ei.OutlookComments,

                    PreparednessScore = ei.PreparednessScore,
                    PreparednessText = ei.PreparednessText,
                    PreparednessComments = ei.PreparednessComments

                }).FirstOrDefault();
            return eventIndicator;
        }

        public List<EventIndicatorDto> GetEventIndicatorsFromControversy(Guid idCompany, Guid idControversy)
        {
            throw new NotImplementedException();
        }


        public List<IncidentDto> GetIncidentsData(Guid idCompany, Guid idEventIndicator, Guid idControversyIndicator, ListOptions options = null)
        {

            //TODo take into considerations that not all param are using and options need to be used
            var filtredIncidents = _incidentBl.ReadWhere(
                i => i.CompanyId == idCompany && i.EventIndicatorId == idEventIndicator, null, null,
                a => a.IncidentId, a => a.Type,
                a => a.Name,
                a => a.Source,
                a => a.IncidentDate,
                a => a.Summary,
                a => a.Analysis,
                a => a.ImpactScore,
                a => a.ImpactTrend,
                a => a.RiskScore,
                a => a.RiskTrend,
                a => a.MainTag,
                a => a.ExternalUrl,
                a => a.IsMajorIncident)
                .Select(i => new IncidentDto()
                {
                    Id = i.Id,

                    IncidentId = i.IncidentId,
                    IsMainIncident = false,
                    IsMajorIncident = i.IsMajorIncident,

                    Type = (int)i.Type,
                    Name = i.Name,

                    Source = i.Source,
                    IncidentDate = i.IncidentDate.Date,

                    Summary = i.Summary,
                    Analysis = i.Analysis,
                    MainTag = i.MainTag,

                    ImpactScore = i.ImpactScore,
                    ImpactTrend = i.ImpactTrend,

                    RiskScore = i.RiskScore,
                    RiskTrend = i.RiskTrend,

                    ExternalUrl = i.ExternalUrl
                }).ToList();


            var incidents = new List<IncidentDto>();

            var incidentsDictionary = filtredIncidents.GroupBy(i => i.IncidentId)
                .ToDictionary(g => g.Key, g => g.OrderByDescending(i => i.IncidentDate).ToList());

            if (incidentsDictionary.Count == 0) return incidents;

            var mainIncidentId = filtredIncidents
                .GroupBy(i => i.IncidentId)
                .Select(group => new
                {
                    IncidentId = group.Key,
                    HighestScore = Math.Max(group.Max(i => i.ImpactScore), group.Max(i => i.RiskScore))
                })
                .OrderByDescending(ig => ig.HighestScore)
                .Select(ig => ig.IncidentId)
                .First();

            // add main incident group
            incidents.AddRange(incidentsDictionary[mainIncidentId]);

            incidents.ForEach(i =>
            {
                i.IsMainIncident = i.IsMajorIncident;
            });

            var incidentGroups = filtredIncidents.GroupBy(i => i.IncidentId)
                .Select(group => new
                {
                    IncidentId = group.Key,
                    IncidentDate = group.Max(i => i.IncidentDate)
                })
                .AsEnumerable();

            if (incidentGroups.Any())
            {
                var incidentIds = incidentGroups.Where(i => i.IncidentId != mainIncidentId)
                    .OrderByDescending(ig => ig.IncidentDate)
                    .Select(ig => ig.IncidentId)
                    .ToList();

                // add next incident groups ordered by incident date
                incidentIds.ForEach(id =>
                {
                    incidents.AddRange(incidentsDictionary[id]);
                });
            }

            return incidents;
        }

        public IncidentDto GetIncidentData(Guid idCompany, Guid idEventIndicator, Guid idControversyIndicator, Guid idIncident)
        {
            throw new NotImplementedException();
        }

        public List<MapDataDto> GetIncidentsMapLocationByEventIndicator(Guid idCompany, Guid idControversy /*currently not used */, Guid idEventIndicator, MapOptions mapOption) //on event
        {
            if (mapOption == null) throw new ArgumentNullException("mapOption", "The map options can not be null");
            //TODO use idControversy
            var dataSource = _incidentBl.ReadWhere(a => a.CompanyId == idCompany && a.EventIndicatorId==idEventIndicator && a.Type==(IncidentType)mapOption.Type, null, null,
                    incident => incident.Id, incident => incident.CompanyId, incident => incident.EventIndicatorId,
                    incident => incident.IncidentId, incident => incident.Type, incident => incident.Name,
                    incident => incident.IncidentDate, incident => incident.Location).ToList();

            var incidentData = dataSource.Where(a=>a.Location!=null).GroupBy(a => new
            {
                a.Location.Address
            }).ToList();

            var list=new List<MapDataDto>();
            MapDataDto aux;
            foreach (var igroup in incidentData)
            {
                
                aux=new MapDataDto() //TODO refactor stelian
                {
                    GeoData = new LocationDto()
                    {
                        Address = igroup.Key.Address,
                        Latitude = igroup.FirstOrDefault(a=>a.Location!=null).Location.Latitude,
                        Longitude = igroup.FirstOrDefault(a => a.Location != null).Location.Longitude
                    },
                    MapLocationType = MapLocationType.Incident
                };
                aux.DetailsToolTip = GetMapDataDtoDetails(igroup.OrderByDescending(a => a.IncidentDate).ToList());
                list.Add(aux);

            }

            return list;
        }

        public List<MapDataDto> GetIncidentsMapLocationForCompany(Guid idCompany, MapOptions mapOption) //in view summary
        {
            if(mapOption==null) throw new ArgumentNullException("mapOption","The map options can not be null");
            var companyData = _corporateDataBl.ReadWhere(a => a.Id == idCompany).FirstOrDefault();
            if (companyData == null) throw new CorporateDataNotFoundException();

            var list = new List<MapDataDto>();

            var incidentData =
                _incidentBl.ReadWhere(a => a.CompanyId == idCompany && a.Type==(IncidentType)mapOption.Type && a.Location!=null, null, null,incident=>incident.Id,
                    incident => incident.CompanyId, incident => incident.EventIndicatorId,
                    incident => incident.IncidentId, incident => incident.Type, incident => incident.Name,
                    incident => incident.IncidentDate, incident => incident.Location).GroupBy(a => new
                    {
                        a.Location.Address
                    }).ToList();
            
            
            MapDataDto aux;

            foreach (var igroup in incidentData)
            {
                
                aux=new MapDataDto()
                {
                    GeoData = new LocationDto()
                    {
                        Address = igroup.Key.Address,
                        Latitude = igroup.FirstOrDefault(a=>a.Location!=null).Location.Latitude,
                        Longitude = igroup.FirstOrDefault(a => a.Location != null).Location.Longitude
                    },
                    MapLocationType = MapLocationType.Incident
                };
                aux.DetailsToolTip = GetMapDataDtoDetails(igroup.OrderByDescending(a => a.IncidentDate).ToList());
                list.Add(aux);

            }

            if (companyData.HeadquarterLatitude.HasValue && companyData.HeadquarterLongitude.HasValue)
                list.Add(new MapDataDto()
                {
                    GeoData = new LocationDto() { Address = companyData.Address, Latitude = companyData.HeadquarterLatitude.Value, Longitude = companyData.HeadquarterLongitude.Value },
                    MapLocationType = MapLocationType.HQ
                });

            return list;
        }

        private List<MapDataDtoDetails> GetMapDataDtoDetails(IList<Incident> incidents)
        {
            var list = new List<MapDataDtoDetails>();
            foreach (var incident in incidents)
            {
                var themeName = string.Empty;
                var controversyIndicator = string.Empty;
                var eventIndicator = string.Empty;
                var eIndicator =
                    _eventIndicatorGroupBl.ReadAllWhere(a => a.Id == incident.EventIndicatorId,null,1)
                        .FirstOrDefault();
                if (eIndicator != null)
                {
                    eventIndicator = eIndicator.BaseEventIndicatorId.ToString();
                    controversyIndicator = eIndicator.ControversyIndicatorId.ToString();
                    var indicatorTheme =
                        _indicatorTemplateBl.ReadAllWhere(a => a.Id == eIndicator.ControversyIndicatorId, null, 1)
                            .FirstOrDefault();
                    if (indicatorTheme != null)
                    {
                        themeName = indicatorTheme.ThemeName;
                    }
                }
                list.Add(new MapDataDtoDetails()
                {
                    Name = incident.Name,
                    Link = string.Format("{0};{1};{2};{3};{4};{5}",themeName,controversyIndicator,eventIndicator,eIndicator.Id,incident.IncidentId,incident.Id)
                });
            }
            return list;

        }
    }


}
